/*
 * producer_consumer.c
 *
 *  Created on: May 8, 2009
 *      Author: lapoija
 */

#include <pthread.h>
#include "producer_consumer.h"
#include "thread_extensions.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include "log.h"

/*
 * Function definitions
 */
void initLifoList(request_list_t * out_data);
int add(request_t * in_data);
int remove(request_t * out_data);
unsigned int count();

/*
 * File visible variables
 */
request_list_container_t _req_container = { .isInitialized=false };
pthread_mutexattr_t mutexAttr;
pthread_condattr_t condAttr;
pthread_mutex_t sharedMutex;
pthread_mutexattr_t sharedMutexAttr;

/*
 * Initialize the mutex structure -> should only have to call this once for the
 * lifetime of the process.
 */
void initMutex()
{
   PTHREAD_MUTEXATTR_INIT(&sharedMutexAttr);
   pthread_mutex_init(&sharedMutex, &sharedMutexAttr);
   logInitMutex();
}

/*
 * Add a request_t structure to the tail of the linked list.
 */
int add(request_t * in_data)
{
   pthread_mutex_lock(&sharedMutex);

   if (!(_req_container.isInitialized))
   {
      pthread_mutex_unlock(&sharedMutex);

      return LIST_NULL;
   }

   if (_req_container.count >= _req_container.max_length)
   {
      pthread_mutex_unlock(&sharedMutex);

      return LIST_FULL;
   }

   // check for empty list
   if (_req_container.dataHead == NULL)
   {
      _req_container.dataHead = (request_list_t *)malloc(sizeof(request_list_t));
      _req_container.dataHead->data = NULL;
      _req_container.dataHead->next = NULL;
      _req_container.dataHead->prev = NULL;
   }

   // check for 1'st record; e.g. _req_container.count == 0
   if (_req_container.dataHead->data == NULL)
   {
      _req_container.dataHead->data = in_data;
      _req_container.dataTail = _req_container.dataHead;
      // next and prev already set to NULL in preceding operations.
      ++(_req_container.count);

      pthread_mutex_unlock(&sharedMutex);

      return OPERATION_SUCCESS;
   }

   // get a pointer to the last data item in the list
   request_list_t * currList = _req_container.dataTail;

   // allocate space for the new item of the linked list.
   request_list_t * newList = (request_list_t *)malloc(sizeof(request_list_t));

   // attach the new item to the end of the linked list and increment the count.
   newList->data = in_data;
   newList->next = NULL;
   newList->prev = currList;
   currList->next = newList;
   _req_container.dataTail = newList;
   ++(_req_container.count);

   pthread_mutex_unlock(&sharedMutex);

   return OPERATION_SUCCESS;
}

/*
 * Remove a request_t data structure from the tail of the linked list;
 * NOTE: a shallow copy is used to copy the data from the tail of the
 *       linked list into the output parameter.  The linked list data
 *       is freed and nulled.
 */
int remove(request_t * out_data)
{
   pthread_mutex_lock(&sharedMutex);

   if (out_data == NULL)
   {
      pthread_mutex_unlock(&sharedMutex);

      return OUT_DATA_NULL;
   }

   if (!(_req_container.isInitialized))
   {
      pthread_mutex_unlock(&sharedMutex);

      return LIST_NULL;
   }

   if (_req_container.dataHead == NULL)
   {
      pthread_mutex_unlock(&sharedMutex);

      return LIST_EMPTY;
   }

   request_list_t *currList = NULL;
   request_list_t *prevList = NULL;

   if (_req_container.count == 1)
   {
      currList = _req_container.dataHead;
   }
   else
   {
      // find the last and 2'nd to last item of the linked list.
      currList = _req_container.dataTail;
      prevList = _req_container.dataTail->prev;

      // pop item off of the list.
      prevList->next = NULL;
   }

   // shallow copy last item into the out-parameter to return to the caller.
   memcpy(out_data, currList->data, sizeof(request_t));

   // decrement the count
   --(_req_container.count);

   // free the popped data's memory.
   free(currList->data);
   currList->data = NULL;
   free(currList);
   currList = NULL;

   // ensure that if the last item has been removed (e.g. the head of our
   // linked list), that we set the data member of the _req_container to
   // NULL to signify that the list is empty.
   if (_req_container.count == 0)
   {
      _req_container.dataHead = NULL;
      _req_container.dataTail = NULL;
   }
   else
   {
      _req_container.dataTail = prevList;
   }

   pthread_mutex_unlock(&sharedMutex);

   return OPERATION_SUCCESS;
}

/*
 * Returns the number of items currently in the linked list.
 */
unsigned int count()
{
   pthread_mutex_lock(&sharedMutex);

   if (!(_req_container.isInitialized))
   {
      pthread_mutex_unlock(&sharedMutex);

      return LIST_NULL;
   }

   pthread_mutex_unlock(&sharedMutex);

   return _req_container.count;
}

/*
 * Initializes the linked list, sets the max size of the linked list
 * buffer, and returns a pointer to the linked list wrapper data
 * structure.
 */
request_list_container_t * initContainer(unsigned int maxLength)
{
   return initContainerForce(maxLength, false);
}

/*
 * Initializes the linked list, sets the max size of the linked list
 * buffer, and returns a pointer to the linked list wrapper data
 * structure.
 *
 * Parameters:
 *   isForce - if true, then blindly initializes the linked list, possibly
 *             overwriting data; if false, then only initializes an
 *             unIntialized linked list structure.
 */
request_list_container_t * initContainerForce(unsigned int maxLength,
      bool isForce)
{
   pthread_mutex_lock(&sharedMutex);

   if (isForce || !(_req_container.isInitialized))
   {
      _req_container.isInitialized = true;
      _req_container.dataHead = (request_list_t *)malloc(sizeof(request_list_t));
      _req_container.dataHead->data = NULL;
      _req_container.dataHead->next = NULL;
      _req_container.dataHead->prev = NULL;
      _req_container.dataTail = NULL;
      _req_container.add = add;
      _req_container.remove = remove;
      _req_container.max_length = maxLength;
      _req_container.count = 0;

      PTHREAD_MUTEXATTR_INIT(&mutexAttr);
      PTHREAD_CONDATTR_INIT(&condAttr);

      pthread_mutex_init(&(_req_container.mutex), &mutexAttr);
      pthread_cond_init(&(_req_container.producerCond), &condAttr);
      pthread_cond_init(&(_req_container.consumerCond), &condAttr);
   }

   pthread_mutex_unlock(&sharedMutex);

   return &_req_container;
}
